// getComic project main.go
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"strings"
	"sync"
	"time"

	. "github.com/soekchl/myUtils"
)

type jsonFormat struct {
	StartNum    int           `json:"startNum"`  // 开始创建编号
	Timeout     time.Duration `json:"timeout"`   // 开始创建编号
	Sleeptime   time.Duration `json:"sleeptime"` // 中间睡眠时间
	Key         string        `json:"key"`       // 解析home页面的时候 从关键字开始查找 reqlist 内容
	ComicHome   string        `json:"comicHome"`
	HeaderUrl   string        `json:"HeaderUrl"`
	DownloadUrl string        `json:"downloadUrl"`
	IfUseNum    bool          `json:"ifUseNumSort"` // comic list if use number sort title
	ReqList     []string      `json:"ReqList"`      // 0-章节名称 1-下载链接
}

type tempStruct struct {
	url      string
	fileName string
}

var (
	wg          sync.WaitGroup
	info        jsonFormat
	jsonFile    = flag.String("json", "format.json", "json配置文件")
	specialFlag = flag.Bool("special", true, "是否需要特殊下载图片 默认 true")
	outPutMod   = flag.Int("mode", 0, "0-读取json下载漫画 \n1-读取json只生成reqList \n2-按照json中reqList下载漫画")

	li   = [2]string{`<li>`, `</li>`}
	href = [2]string{`href="`, `"`}
	span = [2]string{`<span>`, `</span>`}

	errDown      = make(map[int][]tempStruct) // index,需要下载数据内容的资料
	okDown       = make(map[int]bool)
	errDownMutex sync.Mutex
	okDownMutex  sync.Mutex

	dc  = 0
	all = 0
)

func init() {
	info = jsonFormat{
		Key: `<span>章节</span>`,
		// ComicHome: "https://www.gufengmh8.com/manhua/bailianchengshen/",
		// HeaderUrl: "https://www.gufengmh8.com",
		// DownloadUrl: "https://res.gufengmh8.com/",

		ComicHome:   "https://www.manhuaniu.com/manhua/5965/",
		HeaderUrl:   "https://www.manhuaniu.com",
		DownloadUrl: "https://res.nbhbzl.com/",
		// DownloadUrl: "https://restp.dongqiniqin.com/",

		ReqList: []string{
			"/manhua/2652/54365.html",
			"/manhua/2652/388096.html",
		},
	}
	flag.Parse()
}

func showErrordown() {
	errDownMutex.Lock()
	defer errDownMutex.Unlock()

	sum := 0
	for _, v := range errDown {
		sum += len(v)
	}
	Noticef("\n\t (%v / %v)\n", all-sum-dc, all)
}

func main() {
	loadJson(*jsonFile)
	go func() {
		for {
			str := ""
			fmt.Scanf("%s\n", &str)
			showErrordown()
		}
	}()

	if strings.Index(info.ComicHome, "newtoki") >= 0 {
		(&NewtokiStruct{}).run()
	} else if strings.Index(info.ComicHome, "cocolamanhua") >= 0 ||
		strings.Index(info.ComicHome, "godamanga") >= 0 {
		(&GodaStruct{}).run()
	} else {
		d := &Dumanhhua{
			li:       [2]string{`<li>`, `</li>`},
			href:     [2]string{`href="`, `"`},
			span:     [2]string{`<span>`, `</span>`},
			errDown:  make(map[string]string),
			missPage: make(map[int][2]string),
		}
		d.start()
	}
}

func normal() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)

	go func() {
		<-c
		if len(errDown) > 1 {
			fmt.Printf("发生异常的数据：%v", errDown)
		}

		str := ""
		m := 0
		for i := info.StartNum; i < info.StartNum+len(info.ReqList); i++ {
			okDownMutex.Lock()
			if !okDown[i] {
				str += fmt.Sprint(i, ",")
				m++
			}
			okDownMutex.Unlock()
		}
		Noticef("总共：%v 未完成=%v 列表=%v", len(info.ReqList), m, str)
		os.Exit(0)
	}()

	loadJson(*jsonFile)

	switch *outPutMod {
	case 1:
		getNumberReqList()
	case 2:
		Notice("读取json只生成reqList")
		getQimanReqList() // 奇漫网 http://www.qiman5.com/
		// getNumberReqList()
		// getWLDFReqList() // 武炼巅峰 专用
		outPutJson()
		Notice("已生成json")
		os.Exit(0)
	}

	Notice("开始下载漫画")

	for n, v := range info.ReqList {
		if n&1 == 0 {
			continue
		}
		k := n / 2
		if len(v) < 10 {
			okDownMutex.Lock()
			okDown[info.StartNum+k] = true
			okDownMutex.Unlock()
			Notice(info.StartNum+k, " 不下载跳过")
			continue
		}
		wg.Add(1)
		go func(kk int, u string) {
			for i := 0; i < 10 && !getOne(kk, u); i++ {
			}
			wg.Done()
		}(k+info.StartNum, info.HeaderUrl+v)
	}
	wg.Wait()

	checkErrorDown()
}

func checkErrorDown() {
	if len(errDown) < 1 {
		return
	}
	defer checkErrorDown()

	showErrordown()
	str := ""
	errDownMutex.Lock()
	for k, _ := range errDown {
		str += fmt.Sprintf("%v %v个", k, len(errDown[k]))
	}

	fmt.Printf("发生异常的数据：%v 尝试从新下载错误数据\n", str)

	temp := errDown
	errDown = make(map[int][]tempStruct) // 更新errDown
	errDownMutex.Unlock()
	// 开始把错误数据从新下载
	for k, v := range temp {
		for _, vv := range v {
			wg.Add(1)
			go func(url, fileName string) {
				defer wg.Done()
				_, err := downloadImages(url, fileName)
				if err != nil {
					Errorf("m=%v fileName=%v err=%v", k, fileName, err)
					errDownMutex.Lock()
					errDown[k] = append(errDown[k], tempStruct{
						url:      url,
						fileName: fileName,
					})
					errDownMutex.Unlock()
				}
			}(vv.url, vv.fileName)
		}
	}
	wg.Wait()
}

// 文字转数字 第一话-->1   第150话-->150
func getNumberToStr(str string) (num int) {
	if strings.Index(str, "番外") >= 0 {
		return 0
	}
	if strings.Index(str, "最终") >= 0 ||
		strings.Index(str, "完结") >= 0 {
		return -2
	}
	f := false
	for _, v := range str {
		// 数字
		if v >= rune('0') && rune('9') >= v {
			f = true
			num = num*10 + int(v-'0')
			continue
		}

		// 汉字
		n := changeNumer(v)
		if f && n == -1 {
			if v == rune('年') && num > 0 { // NOTE 题目中存在年
				Warnf("---getNumberToStr(%s) = %v", str, num)
				return 0
			}
			return
		}
		if n >= 0 {
			f = true
			if n > 10 {
				num *= 10
			} else if n == 10 {
				if num%10 == 0 {
					num = num*10 + 10
				} else {
					num *= 10

				}
			} else {
				num += n
			}
		}
	}
	return
}

// 转换汉字
func changeNumer(r rune) int {
	switch r {
	case rune('一'):
		return 1
	case rune('二'):
		return 2
	case rune('三'):
		return 3
	case rune('四'):
		return 4
	case rune('五'):
		return 5
	case rune('六'):
		return 6
	case rune('七'):
		return 7
	case rune('八'):
		return 8
	case rune('九'):
		return 9
	case rune('十'):
		return 10
	case rune('百'):
		return 100
	case rune('千'):
		return 100
	}
	return -1
}

// 武炼巅峰 专用
func getWLDFReqList() {
	code, body, err := httpReq(info.ComicHome, "get", nil, make(url.Values)) // 请求主网址内容
	if err != nil {
		Error("getReqList " + err.Error())
		os.Exit(1)

	}

	if code != 200 {
		Error(fmt.Sprintf("getReqList  code=%v err=%s", code, body))
		os.Exit(1)
	}

	// 跳到关键字
	n := strings.Index(body, info.Key)
	if n < 1 {
		Error("关键字未找到")
		os.Exit(1)
	}
	body = body[n:]
	al := make(map[string]string)
	var reqlist []string

	for {
		// 获取一截 整体 li 控件
		liStr, stopIndex := getString(body, li)
		if stopIndex < 0 { // 没有li控件
			break
		}
		hrefStr, _ := getString(liStr, href)
		spanStr, _ := getString(liStr, span)
		// 检查里面 有 href 和 span  就截取 保存
		if len(hrefStr) < 1 || len(spanStr) < 1 {
			break
		}

		if strings.Index(spanStr, ".") >= 0 {
			al[strings.Split(spanStr, ".")[1]] = hrefStr
		} else {
			al[spanStr] = hrefStr
		}
		body = body[stopIndex:] // 截断
	}
	for k, v := range get武炼巅峰titleList() {
		reqlist = append(reqlist, v) // span
		if len(al[v]) < 1 {
			Debug("缺少：", k, v)
		}
		reqlist = append(reqlist, al[v])
	}

	Noticef("总共：%v", len(reqlist)/2)
	info.ReqList = reqlist
}

// 通用 有数字就转换
func getNumberReqList() {
	code, body, err := httpReq(info.ComicHome, "get", nil, make(url.Values)) // 请求主网址内容
	if err != nil {
		Error("getReqList " + err.Error())
		os.Exit(1)

	}

	if code != 200 {
		Error(fmt.Sprintf("getReqList  code=%v url=%v  err=%s", code, info.ComicHome, body))
		os.Exit(1)
	}

	// 跳到关键字
	n := strings.Index(body, info.Key)
	if n < 1 {
		Error("关键字未找到")
		os.Exit(1)
	}
	body = body[n:]
	var spans []string
	var hrefs []string
	for {
		// 获取一截 整体 li 控件
		liStr, stopIndex := getString(body, li)
		if stopIndex < 0 { // 没有li控件
			break
		}
		hrefStr, _ := getString(liStr, href)
		spanStr, _ := getString(liStr, span)
		// 检查里面 有 href 和 span  就截取 保存
		if len(hrefStr) < 1 || len(spanStr) < 1 {
			break
		}

		hrefs = append(hrefs, hrefStr)
		spans = append(spans, spanStr)

		body = body[stopIndex:] // 截断
	}

	mm := make(map[int]string)
	mms := make(map[int]string)
	n = 0
	max := 0
	var reqlist []string
	var tempReq []string

	// 检查是否可以数字化
	for i := 0; i < len(spans); i++ {
		if strings.Index(spans[i], "活动") >= 0 {
			continue
		}
		if strings.Index(spans[i], "通知") >= 0 {
			continue
		}
		if getNumberToStr(spans[i]) > max { // 查找最大值
			max = getNumberToStr(spans[i])
		}
		mms[getNumberToStr(spans[i])] = spans[i]
		mm[getNumberToStr(spans[i])] = hrefs[i]
		tempReq = append(tempReq, spans[i])
		tempReq = append(tempReq, hrefs[i])
	}

	var nh []int
	if len(spans)-len(mm) < 20 { // 估量为 中间20个 公告等等
		// 数字化
		for i := 1; i <= max; i++ {
			reqlist = append(reqlist, mms[i])
			if len(mm[i]) > 0 {
				reqlist = append(reqlist, mm[i])
			} else {
				reqlist = append(reqlist, fmt.Sprint(i))
				n++
				nh = append(nh, i)
			}
		}

	} else {
		Debug("按照顺序存储")
		// 按照顺序存储
		reqlist = tempReq
	}

	Noticef("总共：%v 缺少：%v %v", len(reqlist)/2, n, nh)
	info.ReqList = reqlist
}

func getString(src string, mark [2]string) (str string, stopIndex int) {
	startIndex := strings.Index(src, mark[0])
	if startIndex < 0 { // 结束
		return "", -1
	}
	stopIndex = strings.Index(src[startIndex+len(mark[0]):], mark[1])
	if stopIndex < 0 {
		return "", -2
	}
	stopIndex += startIndex + len(mark[0])
	return src[startIndex+len(mark[0]) : stopIndex], stopIndex
}

func outPutJson() {
	buff, err := json.MarshalIndent(&info, "", "  ")
	if err != nil {
		Error(err)
		return
	}
	outFile(*jsonFile, string(buff))
}

func loadJson(fileName string) {
	buff := readFile(fileName)
	if len(buff) < 1 {
		outPutJson()
		Error("File is Empty ", fileName)
		os.Exit(1)
	}
	err := json.Unmarshal(buff, &info)
	if err != nil {
		Error(err)
		os.Exit(1)
	}
}

// 获取一话完整图片
func getOne(m int, urlReq string) (errFlag bool) {
	defer func() {
		okDown[m] = true
		Notice("\t\t\t", m, " over")
	}()

	headers := make(map[string]string)
	headers["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3"
	headers["Cookie"] = "UM_distinctid=16be3fa014c951-0d0f815ec8a01a-1a201708-1fa400-16be3fa014dbfe; CNZZDATA1273814033=1260435515-1562896174-https%253A%252F%252Fwww.baidu.com%252F%7C1562896174"
	headers["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36"
	code, body, err := httpReq(urlReq, "get", nil, make(url.Values)) // 请求主网址内容
	if err != nil {
		Error(err)
		return
	}
	if code != 200 {
		if len(body) > 1024 {
			Errorf("code=%v url=%v body=%s", code, urlReq, body[:1024])
		} else {
			Errorf("code=%v url=%v body=%s", code, urlReq, body)
		}

		return
	}
	// list := getImages(body) // 从主网址中分离目标图表
	list := getImages2(body) // 从主网址中分离目标图表
	Noticef("漫画=%v 个数=%v", m, len(list))
	dir := fmt.Sprint("./comic/", m, "/")
	err = os.MkdirAll(dir, os.ModePerm)
	if err != nil {
		Error(err)
		return
	}
	n := 1
	for _, v := range list { // 一个一个下载
		_, err = downloadImages(v, fmt.Sprintf("%vIMGE_%v.jpeg", dir, n))
		// err = downloadImages(info.DownloadUrl+v, fmt.Sprintf("%vIMGE_%v.jpg", dir, n))
		if err != nil {
			Errorf("m=%v n=%v err=%v", m, n, err)
			errDownMutex.Lock()
			errDown[m] = append(errDown[m], tempStruct{
				url:      info.DownloadUrl + v,
				fileName: fmt.Sprintf("%vIMGE_%v.jpg", dir, n),
			})
			errDownMutex.Unlock()
		}
		n++
	}
	return true
}

// get请求内容 下载到制定文件中
func downloadImages(reqUrl, fileName string) (int, error) {
	dc += 1
	defer func() { dc -= 1 }()
	client := &http.Client{Timeout: time.Duration(info.Timeout * time.Second)}
	req, err := http.NewRequest("GET", reqUrl, nil)
	if err != nil {
		return 0, fmt.Errorf("err=%v url=%v", err, reqUrl)
	}
	req.Header.Add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36")
	response, err := client.Do(req)
	if err != nil {
		return 0, fmt.Errorf("err=%v url=%v", err, reqUrl)
	}
	if response.Body != nil {
		defer response.Body.Close()
	}
	if response.StatusCode != http.StatusOK {
		return response.StatusCode, fmt.Errorf("err=%v url=%v", err, reqUrl)
	}
	fi, err := os.Create(fileName)
	if err != nil {
		return 0, fmt.Errorf("err=%v url=%v", err, reqUrl)
	}
	defer fi.Close()
	_, err = io.Copy(fi, response.Body)
	if err != nil {
		return 0, err
	}
	return http.StatusOK, nil
}

// 从读取出来的 html中查找 chapterImages = [] 内容
func getImages(body string) (imgList []string) {
	n := strings.Index(body, "chapterImages")
	start := strings.Index(body[n+1:], "[")
	stop := strings.Index(body[n+1:], "]")
	jsonStr := body[start+n : n+stop+2]

	err := json.Unmarshal([]byte(jsonStr), &imgList)
	if err != nil {
		Error(err)
	}

	if *specialFlag {
		// 其他特殊情况下需要 图片下载地址前加上参数
		n = strings.Index(body, "chapterPath")
		start = strings.Index(body[n+1:], "\"")
		stop = strings.Index(body[n+start+2:], "\"")
		key := body[start+n+2 : n+stop+start+2]
		for k, _ := range imgList {
			imgList[k] = key + imgList[k]
		}
	}
	return
}

// 从读取出来的 html中查找 chapterImages = [] 内容
func getImages2(body string) (imgList []string) {
	start := strings.Index(body, "eval(function(")
	stop := strings.Index(body[start:], "</script>")
	if start == -1 || stop == -1 {
		Error(body)
		return
	}
	jsCode := body[start : start+stop]
	if len(jsCode) < 335 {
		Notice(jsCode)
		return
	}
	//	key := jsCode[330:334]
	//Noticef("_%v_", key)
	//	key = strings.Replace(key, " ", "", 1)
	//jsCode = jsCode[:330] + key + jsCode[334:]
	rpidx := strings.Index(jsCode, `["`)
	if rpidx != -1 {
		tmp1 := jsCode[rpidx-5 : rpidx]
		tmp2 := strings.Replace(tmp1, " ", "", 1)
		jsCode = strings.Replace(jsCode, tmp1, tmp2, 1)
	}
	jsCode = strings.Replace(jsCode, "d b", "db", 1)
	jsCode = strings.Replace(jsCode, "a c", "ac", 1)
	//Notice(jsCode)
	str, err := runJsCode(jsCode)
	//	Notice(str)
	if err != nil {
		Error(body)
		Error(jsCode)
		Error(err)
		os.Exit(1)
	}
	if len(str) < 32 {
		jsCode = strings.Replace(jsCode, "d b", "db", 1)
		str, err = runJsCode(jsCode)
		if len(str) < 1 {
			Errorf("err=%v str=%v", err, str)
			os.Exit(1)
		}
	}

	//	Notice(str)
	if len(str) > 3 {
		str = str[1 : len(str)-1]
	}
	tmp := strings.Split(str, " ")
	for _, v := range tmp {
		imgList = append(imgList, v)
	}
	return
}

// http 请求通用
func httpReq(reqUrl, method string, headers map[string]string, value url.Values) (code int, body string, err error) {
	method = strings.ToUpper(method)
	client := &http.Client{Timeout: time.Duration(info.Timeout * time.Second)}
	val := value.Encode()
	if method == "GET" {
		reqUrl = fmt.Sprintf("%s?%v", reqUrl, val)
		val = ""
	}
	req, err := http.NewRequest(method, reqUrl, strings.NewReader(val))
	if err != nil {
		return 0, "", err
	}
	for k, v := range headers {
		req.Header.Add(k, v)
	}
	response, err := client.Do(req)
	if err != nil {
		return 0, "", err
	}
	if response.Body != nil {
		defer response.Body.Close()
	}
	b, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return 0, "", err
	}
	code = response.StatusCode
	body = string(b)
	return
}

func readFile(fileName string) []byte {
	fi, err := os.Open(fileName)
	if err != nil {
		Error(fileName, err)
		return nil
	}
	defer fi.Close()
	buff, _ := ioutil.ReadAll(fi)
	return buff
}

func outFile(fileName, outFileStr string) {
	fi, err := os.Create(fileName)
	if err != nil {
		Error(err)
		return
	}
	fi.WriteString(outFileStr)
	fi.Close()
}
